use super::*;

pub(crate) struct Args {
	pub name: Option<Path>,
	pub owner: Option<Path>,
	pub constructor: bool,
}
impl parse::Parse for Args {
	fn parse(input: parse::ParseStream) -> Result<Self> {
		let mut name = None;
		let mut owner = None;
		let mut constructor = false;
		let metas = input.parse_terminated(syn::Meta::parse, Token![,])?;
		for meta in metas {
			match meta {
				Meta::NameValue(v) => match (&v.path, &v.value) {
					(n, Expr::Path(v)) if n.is_ident("name") => name = Some(v.path.clone()),
					(n, Expr::Path(v)) if n.is_ident("owner") => owner = Some(v.path.clone()),
					_ => {}
				},
				Meta::Path(v) if v.is_ident("constructor") => constructor = true,
				_ => {}
			}
		}
		Ok(Args { name, owner, constructor })
	}
}

pub(crate) fn expand(attr: &Args, input: &mut ItemFn) -> TokenStream {
	let ty = &input.sig.ident;
	let syn_name = match attr.name.try_ts().unwrap_or_else(|| input.ts()).lit_cstr() {
		Ok(v) => v,
		Err(e) => return e,
	};
	let args = input
		.sig
		.inputs
		.iter()
		.filter_map(|v| match v {
			FnArg::Typed(pat_type) => Some((&pat_type.pat, &pat_type.ty)),
			_ => None,
		})
		.map(|(ident, ty)| quote! { #ident : #ty });
	let export = match input.sig.receiver() {
		Some(r) => match r.ty.as_ref() {
			Type::Reference(type_reference) => {
				let owner = match &attr.owner {
					Some(v) => v,
					None => {
						return Error::new_spanned(quote! {#input}, "need owner for receiver func")
							.into_compile_error()
							.into();
					}
				};
				match type_reference.mutability {
					Some(_) => quote! {::napih::EXPORT_FUNC!(mut,#owner,#syn_name,#ty(#(#args),*));},
					None => quote! {::napih::EXPORT_FUNC!(ref,#owner,#syn_name,#ty(#(#args),*));},
				}
			}
			_ => {
				return Error::new_spanned(quote! {#input}, "only support &self,&mut self")
					.into_compile_error()
					.into();
			}
		},
		None => match &attr.owner {
			Some(this) => match attr.constructor {
				true => quote! {::napih::EXPORT_FUNC!(constructor,#this,#syn_name,#ty(#(#args),*));},
				false => quote! {::napih::EXPORT_FUNC!(static,#this,#syn_name,#ty(#(#args),*));},
			},
			None => quote! {::napih::EXPORT_FUNC!(#syn_name,#ty(#(#args),*));},
		},
	};
	let block = &input.block;
	#[cfg(feature = "dts")]
	let dts = build_dts(dts(attr, input));
	#[cfg(not(feature = "dts"))]
	let dts = TokenStream::new();
	input.block = parse_quote! {{#export #dts #block}};

	quote! {#input}
}
#[cfg(feature = "dts")]
fn dts(attr: &Args, input: &ItemFn) -> DTSResult {
	let name = attr.name.try_ts().unwrap_or_else(|| input.ts());
	let mut args = vec![];
	for arg in input.sig.inputs.iter() {
		match arg {
			FnArg::Typed(v) => args.push((v.pat.try_ts().ok_or("pat")?, v.ty.try_ts().ok_or("ty")?)),
			_ => {}
		}
	}
	let ret = input.sig.output.try_ts();
	let args_dts = fomat!(for (a,t) in args.iter() { (a.ts()) ": " (t.ts())} separated { ", " });
	let ret = fomat!(if let Some(ret) = ret { ": "(ret.ts()) } else { ": void" });
	Ok(match input.sig.receiver() {
		Some(_) => fomat!((name)"("(args_dts)")"(ret)).owner(name, TY_FUNC, &attr.owner),
		None => match &attr.owner {
			Some(owner) => match attr.constructor {
				true => {
					let args_dts = fomat!(for (a,t) in args[1..].iter() { (a.ts()) ": " (t.ts())} separated { ", " });
					fomat!("constructor""("(args_dts)")").with(name, TY_CONSTRUCTOR, &owner)
				}
				false => fomat!("static "(name)"("(args_dts)")"(ret)).with(name, TY_STATIC_FUNC, &owner),
			},
			None => fomat!("export declare function "(name)"("(args_dts)")"(ret)).without(name, TY_FUNC),
		},
	})
}
